package ace.cmp.security.test.annotation;

import ace.cmp.json.jackson.JacksonUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

/**
 * .
 * @author Caspar
 *
 */
public class WithMockBearerTokenAuthenticationSecurityContextFactory implements WithSecurityContextFactory<WithMockBearerTokenAuthentication> {

  private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
  private final ObjectMapper objectMapper = new ObjectMapper();

  public SecurityContext createSecurityContext(WithMockBearerTokenAuthentication withMockBearerTokenAuthentication) {
    String token = withMockBearerTokenAuthentication.token();
    if (token == null || token.isEmpty()) {
      token = UUID.randomUUID().toString();
    }
    Map<String, Object> attributes = JacksonUtils.parseToMap(withMockBearerTokenAuthentication.attributesJson(), String.class, Object.class);
    List<GrantedAuthority> grantedAuthorities = Arrays.stream(withMockBearerTokenAuthentication.authorities())
        .map(p -> new SimpleGrantedAuthority(p))
        .collect(Collectors.toList());

    Instant issuedAt = withMockBearerTokenAuthentication.issuedAtTimestampInMilli() == 0 ?
        Instant.now()
        : Instant.ofEpochMilli(withMockBearerTokenAuthentication.issuedAtTimestampInMilli());

    Instant expiresAt = withMockBearerTokenAuthentication.expiresAtTimestampInMilli() == 0 ?
        Instant.now().plus(withMockBearerTokenAuthentication.expiresAtPlusInMilli(), ChronoUnit.MINUTES)
        : Instant.ofEpochMilli(withMockBearerTokenAuthentication.expiresAtTimestampInMilli());

    Set<String> scopes = Arrays.stream(withMockBearerTokenAuthentication.scopes())
        .collect(Collectors.toSet());
    DefaultOAuth2AuthenticatedPrincipal auth2AuthenticatedPrincipal = new DefaultOAuth2AuthenticatedPrincipal(attributes, grantedAuthorities);
    OAuth2AccessToken oauth2AccessToken = new OAuth2AccessToken(TokenType.BEARER, token, issuedAt, expiresAt, scopes);
    BearerTokenAuthentication authentication = new BearerTokenAuthentication(auth2AuthenticatedPrincipal, oauth2AccessToken, grantedAuthorities);
    SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
    context.setAuthentication(authentication);
    return context;
  }

  @Autowired(
      required = false
  )
  void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
    this.securityContextHolderStrategy = securityContextHolderStrategy;
  }
}
